package puzzle.game.states;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.AngelCodeFont;
import org.newdawn.slick.Color;
import org.newdawn.slick.Font;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.Music;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.loading.DeferredResource;
import org.newdawn.slick.loading.LoadingList;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import puzzle.core.LevelData;
import puzzle.core.TileFactory;
import puzzle.game.BallPuzzle;
import puzzle.game.MapList;
import puzzle.game.states.LoadingState;
import puzzle.game.states.PuzzleState;
import puzzle.util.MapStream;
import puzzle.util.ResourceManager;
import puzzle.util.TimerAction;

public class LoadingState extends PuzzleState {
  
  public static final int ID = 1;
  
  public LoadingState(BallPuzzle ctx) {
    super(ctx);
  }
  
  private TileFactory tileFactory;
  private MapStream mapStream;
  private MapList gameMaps;
  private MapList userMaps;
  private DeferredResource nextResource = null;
  private boolean done = false;
  private ProgressBar progressBar;
  private Image logo;
  private Color fg = new Color(198, 226, 243);
  private TimerAction timer;
  private Image ballImg;
  private Font f_porky_med;
  private Image smallLogo;
  private Image helpScreen;
  private Sound portSound;
  private Sound transSound;
  private Sound endSound;
  private Music ingameMusic;
  
  private String loadStr = ResourceManager.getString("game.msg.loading",
                                "Loading");
  private String finishedStr = ResourceManager.getString("game.msg.done_loading",
                                "Finished loading");
    
  public void initState(GameContainer container) throws SlickException {    
    setBackground(TileFactory.GROUND_COLOR);
    
    //load logo before deferring resources
    logo = new Image(ResourceManager.getString("img.logo", "data/logo.gif"));
    
    //font definition & image files
    String s1 = ResourceManager.getString("game.font.def", "data/fonts/porky_med.fnt");
    String s2 = ResourceManager.getString("game.font.img", "data/fonts/porky_med.png");
    
    //create the font
    f_porky_med = new AngelCodeFont(s1, s2);
    
    ////////////////////////////////////////////////////////////////
    //defer the resources from here on, loading them individually
    ResourceManager.setDeferredLoading(true);
    
    //Since we're in game, we'll set up the TileFactory with
    //the default icons.
    tileFactory = TileFactory.getDefaultInstance();
    tileFactory.init(); 
    
    //the ball image
    ballImg = new Image(ResourceManager.getString("img.ball", "data/tiles/ball.gif"));
    
    //smaller logo
    smallLogo = new Image(ResourceManager.getString("img.logo_small", "data/logo-small.gif"));
    
    //help screen
    helpScreen = new Image(ResourceManager.getString("img.help", "data/help.gif"));
    
    //MapStream handles XML input/output for map files and lists.
    mapStream = new MapStream(tileFactory);
        
    //sets mouse cursor
    String cstr = ResourceManager.getString("img.cursor", "data/cursor.png");
    container.setMouseCursor(cstr, 1, 1);
    
    //ref for maplist files
    String mlstr = "";
    
    //finds game maplist
    mlstr = ResourceManager.getString("game.maplist", "data/maplist.xml");
    try {
      InputStream mlin = ResourceLoader.getResourceAsStream(mlstr);
      gameMaps = mapStream.loadMapList(mlin); 
    }
    catch (Exception e) {
      Log.error("Can't find game's maplist: "+mlstr);
    }
    
    //finds user maplist
    mlstr = ResourceManager.getString("user.maplist", "ext/maplist.xml");
    final String fstr = mlstr;
    try {
      InputStream mlin = ResourceLoader.getResourceAsStream(mlstr);
      userMaps = mapStream.loadMapList(mlin); 
    }
    catch (Exception e) {
      Log.warn("Can't find user MapList: "+mlstr+"\n\tCreating an empty MapList.");
      final File f = new File(fstr);
      try {
          f.createNewFile();
      } catch (IOException exc) {
          Log.warn("Problem creating new maplist.");
      } catch (SecurityException exc) {
          Log.warn("Can't create new maplist due to security restrictions.");
      }
      userMaps = new MapList();
    }
    
    //ref for sounds
    String soundRef = "";
    
    //loads sounds
    soundRef = ResourceManager.getString("audio.sfx.port", "data/audio/port.ogg");
    portSound = trySound(soundRef);

    //state transition music
    soundRef = ResourceManager.getString("audio.sfx.transition", "data/audio/rap.ogg");
    transSound = trySound(soundRef);

    //end level sound
    soundRef = ResourceManager.getString("audio.sfx.end", "data/audio/end.ogg");
    endSound = trySound(soundRef);

    soundRef = ResourceManager.getString("audio.beat", "data/audio/techno.ogg");
    ingameMusic = tryMusic(soundRef);
    if (ingameMusic!=null) {
      getContext().setMusic(ingameMusic);   
    }
    
    timer = new TimerAction(500);
    timer.setRepeats(false);
    progressBar = new ProgressBar();
  }
  
  private Sound trySound(String ref) {
    try { return new Sound(ref); }
    catch (Exception e) {
      Log.error("Error loading sound '"+ref+"'", e);
      return null;
    }
  }
  
  private Music tryMusic(String ref) {
    try { return new Music(ref); }
    catch (Exception e) {
      Log.error("Error loading music '"+ref+"'", e);
      return null;
    }
  }
  
  public void renderState(GameContainer container, Graphics g) {
    Color old = g.getColor();
    
    float logox = container.getWidth()/2.0f-logo.getWidth()/2.0f;
    float logoy = container.getHeight()/2.0f-logo.getHeight()/2.0f;
    g.drawImage(logo, (int)logox, (int)logoy);
    
    //text stuff
    Font f = g.getFont();
    
    String msg = loadStr;
    
    if (nextResource!=null) {
      if (nextResource instanceof LevelData) {
        LevelData d = (LevelData)nextResource;
        if (d.path!=null&&d.path.length()!=0)
          msg += d.path;
        else
          msg += "user map";
      }
      else {
        msg += nextResource.getDescription();
      }
    }
    else {
      if (ResourceManager.getLoadingList().getRemainingResources()<=0) 
        msg = finishedStr;
      else 
        msg += "resources";
    }
    msg+=" ";
    
    int theight = f.getHeight(msg);
    
    //text position
    float tx = container.getWidth()/2.0f-progressBar.width/2.0f;
    float ty = logoy + logo.getHeight() + 25;
    
    //draws the loading msg
    g.setColor(fg);
    g.drawString(msg, (int)tx, (int)ty);
    
    //progress bar position
    float pbx = tx;
    float pby = ty+theight+2;
    
    //renders progress bar
    progressBar.render(g, (int)pbx, (int)pby);
    
    g.setColor(old);
  }
  
  public void updateState(GameContainer container, int delta) {
    //if the resource exists
    if (nextResource != null) {
      try {
        //try to load the resource
        nextResource.load();
        try { Thread.sleep(10); } catch (Exception e) {}
      } catch (IOException e) {
        throw new RuntimeException("Can't load resource: "+nextResource.getDescription());
      }
      //release
      nextResource = null;
    }
    
    //as long as we still have more things to load
    if (ResourceManager.getLoadingList().getRemainingResources() > 0) {
      nextResource = ResourceManager.getLoadingList().getNext();
    }
    //if we're done loading
    else if (!done) {
      done = true;
      timer.setActive(true);
      timer.restart();
    }
    
    //time for change...
    if (done&&timer.update(delta)) {
      getContext().enterState(MenuState.ID, 
                        getExitTransition(), 
                        getEnterTransition());
    }
  }
  
  public void keyPressed(int key, char c) {
    super.keyPressed(key, c);
    if (key == Input.KEY_ESCAPE) {
      getContext().getContainer().exit();
    }
  }
  
  public boolean isDone() {
    return done;
  }
  
  public final int getID() {
    return ID;
  }
  
  public Sound getEndSound() {
    return endSound;
  }
  
  public Sound getTransitionSound() {
    return transSound;
  }
  
  public Sound getPortSound() {
    return portSound;
  }
  
  public Music getIngameMusic() {
    return ingameMusic;
  }
  
  public Image getHelpImage() {
    return helpScreen;
  }
  
  public Font getMainFont() {
    return f_porky_med;
  }
  
  public Image getBallImage() {
    return ballImg;
  }
  
  public TileFactory getTileFactory() {
    return tileFactory;
  }
  
  public MapStream getMapStream() {
    return mapStream;
  }
  
  public MapList getGameMapList() {
    return gameMaps;
  }
  
  public MapList getUserMapList() {
    return userMaps;
  }
  
  public Image getLogo() {
    return logo;
  }
  
  public Image getSmallLogo() {
    return smallLogo;
  }
  
  private class ProgressBar {
    
    int width = 160;
    int height = 13;
    Color bord;
    Color base;
    Color grad1;
    Color grad2;
    
    public ProgressBar() {
      bord = new Color(42, 76, 98);
      base = new Color(136, 197, 236);
      grad1 = new Color(base);
      grad2 = new Color(0f,0f,0f,0f);
      
      base.a = 0.50f;
    }
    
    public void render(Graphics g, int x, int y) {
      LoadingList ll = ResourceManager.getLoadingList();
      int total = ll.getTotalResources();
      int loaded = ll.getTotalResources() - ll.getRemainingResources();
      
      float bar = loaded / (float)total;
      
      //fills the base rectangle
      g.setColor(base);
      g.fillRect(x, y, (int)(width*bar), height);
      
      //renders a gradient to give a smooth look
      fillGradient(x, y, (int)(width*bar), height);
      
      //draws the border
      g.setColor(bord);
      g.drawRect(x, y, width, height);  
    }
    
    /** Uses direct calls to OpenGL to fill a gradient rectangle. */
    private void fillGradient(int x, int y, int width, int height) {
      Texture.bindNone();
      
      //starting color
      grad1.bind();
      
      GL11.glBegin(GL11.GL_QUADS);
        GL11.glVertex2f(x,y);
        GL11.glVertex2f(x+width,y);
        //ending color
        grad2.bind();
        GL11.glVertex2f(x+width,y+height);
        GL11.glVertex2f(x,y+height);
      GL11.glEnd();
    }
  }
}